using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.IO;
using RideMe.Core;
using System.Collections.Specialized;
using RideMe.Core.Properties;
using RideMe.Presentation.Properties;

namespace RideMe.Presentation.Controls
{
    public partial class ProjectExplorer : UserControl
    {

        /// <summary>
        /// Initializes a new instance of the <see cref="T:ProjectExplorer"/> class.
        /// </summary>
        public ProjectExplorer()
        {
            InitializeComponent();
            HookProjectServiceEvents();
            showAllFilesToolStripButton.Checked = !Options.Instance.ShowAllFiles;
        }

        private bool _renamedHere;

        public event EventHandler ProjectItemFound;

        protected virtual void OnProjectItemFound(EventArgs e)
        {
            if (ProjectItemFound != null)
            {
                ProjectItemFound(this, e);
            }
        }

        private string _projectPath;

        /// <summary>
        /// Gets or sets the project path.
        /// </summary>
        /// <value>The project path.</value>
        public string ProjectPath
        {
            get { return _projectPath; }
            set { _projectPath = value; }
        }

        /// <summary>
        /// Gets a value indicating whether [node selected].
        /// </summary>
        /// <value><c>true</c> if [node selected]; otherwise, <c>false</c>.</value>
        public bool NodeSelected
        {
            get { return projectTreeView.SelectedNode != null; }
        }

        /// <summary>
        /// Gets a value indicating whether [root node selected].
        /// </summary>
        /// <value><c>true</c> if [root node selected]; otherwise, <c>false</c>.</value>
        public bool RootNodeSelected
        {
            get { return projectTreeView.SelectedNode.Parent == null; }
        }

        public bool InModelsNode
        {
            get { return CheckForParentNode(Resources.ModelsFolderName); }
        }

        public bool InViewsNode
        {
            get { return CheckForParentNode(Resources.ViewsFolderName); }
        }

        public bool InControllersNode
        {
            get { return CheckForParentNode(Resources.ControllersFolderName); }
        }

        public bool RubyFileSelected
        {
            get { return FileSystemService.IsRubyFile(SelectedItemPath); }
        }

        /// <summary>
        /// Gets the name of the selected item.
        /// </summary>
        /// <value>The name of the selected item.</value>
        public string SelectedItemName
        {
            get { return projectTreeView.SelectedNode != null ?  projectTreeView.SelectedNode.Text : string.Empty; }
        }

        /// <summary>
        /// Gets the selected item path.
        /// </summary>
        /// <value>The selected item path.</value>
        public string SelectedItemPath
        {
            get { return projectTreeView.SelectedNode != null ? projectTreeView.SelectedNode.Tag.ToString() : string.Empty; }
        }

        public string SelectedItemParentName
        {
            get
            {
                if (!NodeSelected) return string.Empty;
                if (RootNodeSelected) return string.Empty;
                if (projectTreeView.SelectedNode.Parent == null) return string.Empty;

                return projectTreeView.SelectedNode.Parent.Text;
            }
        }

        /// <summary>
        /// Gets a value indicating whether [selected node is a directory].
        /// </summary>
        /// <value>
        /// 	<c>true</c> if [selected node is directory]; otherwise, <c>false</c>.
        /// </value>
        public bool SelectedNodeIsDirectory
        {
            get
            {
                try
                {
                    bool isDirectory = false;
                    if (NodeSelected)
                    {
                        isDirectory = Directory.Exists(projectTreeView.SelectedNode.Tag.ToString()) & projectTreeView.SelectedNode.Tag.ToString().Trim().Length > 0;
                    }

                    return isDirectory;
                }
                // This God Damn error was happening I think as a result
                // of the speed of certain machines. The user went to
                // open a file, and while the file was being read, 
                // the idle event fired, and this check was being 
                // performed causing an accessviolation.
                catch (System.AccessViolationException)
                {
                    return false;
                }
            }
        }

        /// <summary>
        /// Gets a value indicating whether [selected node is testable].
        /// </summary>
        /// <value>
        /// 	<c>true</c> if [selected node is testable]; otherwise, <c>false</c>.
        /// </value>
        public bool SelectedNodeIsTestable
        {
            get
            {
                return FileSystemService.IsRubyFile(SelectedItemPath) || FileSystemService.GetTestableDirectory(SelectedItemPath) != TestableDirectory.NotTestable;
            }
        }
        /// <summary>
        /// Hooks the project service events.
        /// </summary>
        private void HookProjectServiceEvents()
        {
            ProjectService.ProjectPathChanged += new EventHandler<ProjectPathChangedEventArgs>(ProjectService_ProjectPathChanged);
            ProjectService.ProjectClosed += new EventHandler(ProjectService_ProjectClosed);
            ProjectService.FileDeleted += new EventHandler<ProjectFileEventArgs>(ProjectService_FileDeleted);
            ProjectService.FileRenamed += new EventHandler<FileRenameEventArgs>(ProjectService_FileRenamed);

        }

        /// <summary>
        /// Release the event handler resources. Called in dispose.
        /// </summary>
        private void UnhookProjectServiceEvents()
        {
            ProjectService.ProjectPathChanged -= new EventHandler<ProjectPathChangedEventArgs>(ProjectService_ProjectPathChanged);
            ProjectService.ProjectClosed -= new EventHandler(ProjectService_ProjectClosed);
            ProjectService.FileDeleted -= new EventHandler<ProjectFileEventArgs>(ProjectService_FileDeleted);
            ProjectService.FileRenamed -= new EventHandler<FileRenameEventArgs>(ProjectService_FileRenamed);
        }

        /// <summary>
        /// Loads the project.
        /// </summary>
        /// <param name="path">The path.</param>
        public void LoadProject(string path)
        {
            _projectPath = path;
            LoadProject();
        }

        /// <summary>
        /// Loads the project.
        /// </summary>
        public void LoadProject()
        {
            LoadProject(new List<string>());
        }

        /// <summary>
        /// Loads the project and expands the nodes in the openDirectories list.
        /// </summary>
        /// <param name="openDirectories">A list of paths that will be expanded when the project is loaded.</param>
        private void LoadProject(List<string> openDirectories)
        {
            if (InvokeRequired)
            {
                Invoke(new MethodInvoker(
                    delegate()
                    {
                        LoadProject(openDirectories);
                    }));
            }
            else
            {
                TreeViewBuilder.HideExcludedFolders = !showAllFilesToolStripButton.Checked;
                TreeViewBuilder.ProjectItemFound += new EventHandler(TreeViewBuilder_ProjectFolderFound);
                TreeViewBuilder.ProjectPath = _projectPath;
                TreeViewBuilder.PopulateTree(projectTreeView, projectItemsImageList);
                refreshToolStripButton.Enabled = true;
                showAllFilesToolStripButton.Enabled = true;

                if (projectTreeView.Nodes.Count > 0 && projectTreeView.Nodes[0].Nodes.Count > 0)
                {
                    foreach (TreeNode node in projectTreeView.Nodes[0].Nodes)
                    {
                        ExpandSubNodes(openDirectories, node);
                    }
                }
                
                ProjectService.NotifyProjectLoaded(_projectPath);
            }
        }

        /// <summary>
        /// Expands the given node if its path exists in the list.
        /// </summary>
        /// <param name="openDirectories">The list of directory paths that should be expanded.</param>
        /// <param name="node">The node to test for expansion.</param>
        private void ExpandSubNodes(List<string> openDirectories, TreeNode node)
        {
            if (openDirectories.Contains(node.Tag.ToString()))
            {
                node.Expand();
                foreach (TreeNode subNode in node.Nodes)
                {
                    ExpandSubNodes(openDirectories, subNode);
                }
            }
        }

        /// <summary>
        /// Sets the icon.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="imageIndex">Index of the image.</param>
        private static void SetIcon(TreeNode node, int imageIndex)
        {
            // We don't want to change the icon for the root node.
            if (node.Parent != null)
            {
                node.ImageIndex = imageIndex;
            }
        }

        /// <summary>
        /// Deletes the file.
        /// </summary>
        /// <param name="path">The path.</param>
        private void DeleteFileOrFolder(string path)
        {
            try
            {
                if (SelectedNodeIsDirectory)
                {
                    if (Directory.Exists(path))
                    {
                        FileSystemService.DeleteDirectory(path);
                    }
                }
                else
                {
                    if (File.Exists(path))
                    {
                        FileSystemService.DeleteDocument(path);
                    }
                }

                TreeViewBuilder.DeleteNodeByPath(path, projectTreeView);
            }
            catch (IOException)
            {
                MessageService.ShowError(this, Resources.CantDeleteMessage);
            }
        }

        /// <summary>
        /// Opens the project file.
        /// </summary>
        /// <param name="node">The node.</param>
        private void OpenProjectFile(TreeNode node)
        {
            if (!NodeSelected) return;
            if (SelectedNodeIsDirectory) return;
            if (projectTreeView.SelectedNode.Tag.ToString().Trim().Length == 0) return;
            if (!File.Exists(node.Tag.ToString())) return;

            AddProjectFile(node.Tag.ToString());

        }

        /// <summary>
        /// Adds the project file.
        /// </summary>
        /// <param name="file">The file.</param>
        private static void AddProjectFile(string file)
        {
            Cursor.Current = Cursors.WaitCursor;
            ProjectService.AddOpenFile(file);
        }

        /// <summary>
        /// Begins the edit node.
        /// </summary>
        public void BeginEditNode()
        {
            projectTreeView.SelectedNode.BeginEdit();
        }

        /// <summary>
        /// Renames the file system entity.
        /// </summary>
        /// <param name="e">The <see cref="T:System.Windows.Forms.NodeLabelEditEventArgs"/> instance containing the event data.</param>
        private bool RenameFileSystemEntity(NodeLabelEditEventArgs e)
        {
            try
            {
                string oldPath = e.Node.Tag.ToString();

                if (SelectedNodeIsDirectory)
                {
                    e.Node.Tag = Path.Combine(Directory.GetParent(e.Node.Tag.ToString()).FullName, e.Label);
                    ProjectService.RenameDirectory(oldPath, e.Label);

                    TreeViewBuilder.RefreshNodeFiles(e.Node.Tag.ToString(), e.Node);
                }
                else
                {
                    // Use this to denote if the file was renamed in this control. Files
                    // Can be renamed elsewhere, and we need to reflect that.
                    _renamedHere = true;
                    // Set the tag to the new file path. Used to refernce the file path.
                    e.Node.Tag = Path.Combine(Path.GetDirectoryName(e.Node.Tag.ToString()), e.Label);
                    ProjectService.RenameFile(oldPath, e.Label, e.Node.Tag.ToString());
                }
            }
            catch (IOException)
            {
                MessageService.ShowError(this, Resources.CantRenameFileMessage);
                return false;
            }
            return true;
        }

        /// <summary>
        /// Copies the selected file.
        /// </summary>
        public void CopySelectedFile()
        {
            if (!NodeSelected) return;
            if (projectTreeView.SelectedNode.Tag == null) return;

            // I don't really like this code here, but the clipboard object
            // is part of Windows.Systems.Forms, so I guess it should stay in the GUI.
            StringCollection files = new System.Collections.Specialized.StringCollection();

            files.Add(projectTreeView.SelectedNode.Tag.ToString());

            Clipboard.SetFileDropList(files);

        }

        /// <summary>
        /// Pastes the file.
        /// </summary>
        public void PasteFile()
        {
            if (!NodeSelected) return;
            if (!SelectedNodeIsDirectory) return;
            if (projectTreeView.SelectedNode.Tag == null) return;

            StringCollection filesToPaste = Clipboard.GetFileDropList();

            foreach (string file in filesToPaste)
            {
                // The user copied a directory.
                if (Directory.Exists(file))
                {
                    FileSystemService.CopyDirectory(file, Path.Combine(projectTreeView.SelectedNode.Tag.ToString(), Path.GetFileName(file)));
                }
                else // They just copied a file(s)
                {
					string folder = projectTreeView.SelectedNode.Tag.ToString();

					string filename = GetUniqueFilename(folder, Path.GetFileName(file));

                    File.Copy(file, Path.Combine(folder, filename));
                }
            }
        }

		private string GetUniqueFilename(string folder, string originalFilename)
		{
			string filename = originalFilename;

			int copiesAlreadyOnDisk = 0;

			while (File.Exists(Path.Combine(folder, filename)))
			{
				filename = GenerateCopyOfFilename(originalFilename, copiesAlreadyOnDisk);
				copiesAlreadyOnDisk++;
			}

			return filename;
		}

		private string GenerateCopyOfFilename(string baseName, int copies)
		{
			return (copies == 0 ? "Copy of " + baseName : string.Format("Copy ({0}) of {1}", copies+1, baseName));
		}

        private bool CheckForParentNode(string criteria)
        {
            if (!NodeSelected) return false;
            if (RootNodeSelected) return false;

            return ParentNodeMatchesCriteria(criteria);
        }

        /// <summary>
        /// Recursively checks to see if the parent node matches
        /// the specified criteria.
        /// </summary>
        /// <param name="criteria">The node name to look for.</param>
        /// <returns></returns>
        private bool ParentNodeMatchesCriteria(string criteria)
        {
            TreeNode node = projectTreeView.SelectedNode.Parent;

            while (node.Parent != null)
            {
                if (node.Text == criteria)
                {
                    return true;
                }
                node = node.Parent;
            }

            return false;

        }

        public void FocusSpecificNodeByPath(string path)
        {
            if (projectTreeView.Nodes.Count == 0) return;

            // Since the search is recursive the 
            // matchednode has to be reset before
            // attempting to find a node.
            TreeViewBuilder.MatchedNode = null;
            TreeNode foundNode = TreeViewBuilder.FindTreeNode(path, projectTreeView, projectTreeView.Nodes[0]);

            if (foundNode != null)
            {
                projectTreeView.SelectedNode = foundNode;
            }
        }

        /// <summary>
        /// Handles the ProjectPathChanged event of the ProjectService control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:RideMe.Core.ProjectPathChangedEventArgs"/> instance containing the event data.</param>
        private void ProjectService_ProjectPathChanged(object sender, ProjectPathChangedEventArgs e)
        {
            _projectPath = e.NewProjectPath;
            fileSystemWatcher.Path = _projectPath;

            LoadProject();
        }

        /// <summary>
        /// Handles the Created event of the fileSystemWatcher control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:System.IO.FileSystemEventArgs"/> instance containing the event data.</param>
        private void fileSystemWatcher_Created(object sender, FileSystemEventArgs e)
        {
            // Even though this event is supposed to fire for create,
            // when a file is moved it fires as well. Go figure.
            if (e.ChangeType == WatcherChangeTypes.Created)
            {
                // Don't even bother with a session file, yo.
                if (!e.FullPath.Contains(Resources.TempSessions))
                {
                    TreeViewBuilder.AddFoundFile(e.FullPath, Path.GetFileName(e.Name), projectTreeView);
                }
            }
        }

        /// <summary>
        /// Handles the Deleted event of the fileSystemWatcher control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:System.IO.FileSystemEventArgs"/> instance containing the event data.</param>
        private void fileSystemWatcher_Deleted(object sender, FileSystemEventArgs e)
        {
            if (e.ChangeType == WatcherChangeTypes.Deleted)
            {
                TreeViewBuilder.DeleteNodeByPath(e.FullPath, projectTreeView);
                //ProjectService.OpenFiles.Remove(e.FullPath);
                // Call this so if the document is open before it was deleted, 
                // it'll be closed.
                ProjectService.RemoveOpenFile(e.FullPath);
            }
        }

        /// <summary>
        /// Handles the NodeMouseDoubleClick event of the projectTreeView control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:System.Windows.Forms.TreeNodeMouseClickEventArgs"/> instance containing the event data.</param>
        private void projectTreeView_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            OpenProjectFile(e.Node);
        }

        /// <summary>
        /// Handles the ProjectClosed event of the ProjectService control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void ProjectService_ProjectClosed(object sender, EventArgs e)
        {
            if (InvokeRequired)
            {
                Invoke(new MethodInvoker(
                        delegate()
                        {
                            ProjectService_ProjectClosed(sender, e);
                        }));
            }
            else
            {
                TreeViewBuilder.ResetTree(projectTreeView);
                refreshToolStripButton.Enabled = false;
                showAllFilesToolStripButton.Enabled = false;

            }
        }

        /// <summary>
        /// Handles the FileDeleted event of the ProjectService control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:RideMe.Core.ProjectFileEventArgs"/> instance containing the event data.</param>
        private void ProjectService_FileDeleted(object sender, ProjectFileEventArgs e)
        {
            DeleteFileOrFolder(e.FilePath);
        }

        /// <summary>
        /// Handles the AfterLabelEdit event of the projectTreeView control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:System.Windows.Forms.NodeLabelEditEventArgs"/> instance containing the event data.</param>
        private void projectTreeView_AfterLabelEdit(object sender, NodeLabelEditEventArgs e)
        {
            if (!Validator.ValidString(e.Label))
            {
                e.CancelEdit = true;
                return;
            }

            if (!RenameFileSystemEntity(e))
            {
                e.CancelEdit = true;
            }
        }

        /// <summary>
        /// Handles the Click event of the refreshToolStripButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void refreshToolStripButton_Click(object sender, EventArgs e)
        {
            List<String> openDirs = FindOpenDirectories();
            LoadProject(openDirs);
        }

        private List<String> FindOpenDirectories()
        {
            List<string> openDirs = new List<string>();
            if (this.projectTreeView.Nodes.Count == 0)
                return openDirs;

            if (this.projectTreeView.Nodes[0].IsExpanded)
            {
                openDirs.Add(this.projectTreeView.Nodes[0].Tag.ToString());
                AddOpenSubDirectories(openDirs, this.projectTreeView.Nodes[0]);
            }
            return openDirs;
        }

        private void AddOpenSubDirectories(List<string> openDirs, TreeNode treeNode)
        {
            foreach (TreeNode node in treeNode.Nodes)
            {
                if (node.IsExpanded)
                {
                    openDirs.Add(node.Tag.ToString());
                    AddOpenSubDirectories(openDirs, node);
                }
            }
        }

        /// <summary>
        /// Fire the onkeydown for the control when the tree 
        /// keydown event fires.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void projectTreeView_KeyDown(object sender, KeyEventArgs e)
        {
            OnKeyDown(e);
        }

        /// <summary>
        /// Handles the BeforeExpand event of the projectTreeView control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:System.Windows.Forms.TreeViewCancelEventArgs"/> instance containing the event data.</param>
        private void projectTreeView_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {
            if (!e.Node.IsExpanded)
            {
                // Magic numbers are bad!
                SetIcon(e.Node, 2);
            }
        }

        /// <summary>
        /// Handles the BeforeCollapse event of the projectTreeView control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:System.Windows.Forms.TreeViewCancelEventArgs"/> instance containing the event data.</param>
        private void projectTreeView_BeforeCollapse(object sender, TreeViewCancelEventArgs e)
        {
            if (e.Node.IsExpanded)
            {
                // Magic numbers are bad!
                SetIcon(e.Node, 1);
            }
        }

        private void projectTreeView_AfterSelect(object sender, TreeViewEventArgs e)
        {
            string path = string.Empty;

            if (!SelectedNodeIsDirectory)
            {
                path = e.Node.Tag.ToString();
            }
            else
            {
                path = string.Empty;
            }

            ProjectService.ChangeSelectedFile(path);
        }

        private void ProjectService_FileRenamed(object sender, FileRenameEventArgs e)
        {
            // Rename the node if and only if it was renamed from somewhere else (the PropertyGrid, yo).
            if (!_renamedHere)
            {
                // Since the search is recursive the 
                // matchednode has to be reset before
                // attempting to find a node.
                TreeViewBuilder.MatchedNode = null;
                RenameFile(e.OldFilePath, e.NewFileName);
            }

            _renamedHere = false;

        }

        private void RenameFile(string oldFilePath, string newFileName)
        {
            TreeNode nodeToRename = TreeViewBuilder.FindTreeNode(oldFilePath, projectTreeView, projectTreeView.Nodes[0]);
            if (nodeToRename != null)
            {
                nodeToRename.Text = Path.GetFileName(newFileName);
                nodeToRename.Tag = newFileName;
            }
        }

        private void showAllFilesToolStripButton_Click(object sender, EventArgs e)
        {
            Cursor.Current = Cursors.WaitCursor;
            LoadProject();
            Options.Instance.ShowAllFiles = !showAllFilesToolStripButton.Checked;
            Cursor.Current = Cursors.Default;
        }

        private void TreeViewBuilder_ProjectFolderFound(object sender, EventArgs e)
        {
            OnProjectItemFound(EventArgs.Empty);
        }

        private void projectTreeView_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                TreeNode node = projectTreeView.GetNodeAt(e.X, e.Y);
                if (node != null)
                {
                    projectTreeView.SelectedNode = node;
                }
            }
        }

        private void fileSystemWatcher_Renamed(object sender, RenamedEventArgs e)
        {
            RenameFile(e.OldFullPath, e.FullPath);
        }
    }
}
